/*
 * Copyright (C) 2017 XRADIO TECHNOLOGY CO., LTD. All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *    1. Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *    2. Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the
 *       distribution.
 *    3. Neither the name of XRADIO TECHNOLOGY CO., LTD. nor the names of
 *       its contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <string.h>

#include "cmd_util.h"
#include "xz/xz.h"
#include "cmd_xz.h"

#include "fs/fatfs/ff.h"
#include "common/framework/fs_ctrl.h"

#define XZ_DEC_FILE_ENTER(path, f_in, f_out) {									\
			if (fs_ctrl_mount(FS_MNT_DEV_TYPE_SDCARD, 0) != 0) {				\
				CMD_ERR("mount fail\n");										\
			} else {															\
				if (f_open(f_in, path, FA_OPEN_EXISTING | FA_READ) != FR_OK) {	\
					CMD_ERR("open file fail\n");								\
				}																\
				char *temp = path + (strlen(path) - 3);							\
				if (strncmp(temp, ".xz", 3) != 0)								\
					CMD_ERR("open file fail\n");								\
				*temp = '\0';													\
				f_unlink(path);													\
				if (f_open(f_out, path, FA_OPEN_ALWAYS | FA_WRITE) != FR_OK)	\
					CMD_ERR("create file fail\n");								\
			}																	\
		}
#define XZ_DEC_FILE_EXIT(f_in, f_out) {					\
			f_close(f_in);								\
			f_close(f_out);								\
			fs_ctrl_unmount(FS_MNT_DEV_TYPE_SDCARD, 0);	\
		}

FIL fp_in, fp_out;

#define XZ_DEC_INBUF_SIZE 	(4 * 1024)
#define XZ_DEC_OUTBUF_SIZE 	(8 * 1024)
#define XZ_DEC_DICT_MAX   	(32 * 1024)
#define XZ_RATE_COUNT		(1024)

uint8_t xz_array[] = {
	0xFD,0x37,0x7A,0x58,0x5A,0x00,0x00,0x00,0xFF,0x12,0xD9,0x41,0x02,0x01,0x08,0x00,0x21,0x01,0x02,0x00,0x23,0x91,0x59,0x46,0xE0,
	0x06,0x83,0x03,0x71,0x39,0x00,0x17,0x8A,0x7D,0xAD,0xAB,0x52,0xA1,0x44,0xA3,0x72,0xA4,0x56,0x7C,0xF1,0x52,0xE9,0x37,0xBC,0xC4,
	0x60,0xF6,0x45,0xF3,0x75,0x50,0x4E,0x14,0x27,0x9E,0x8F,0x10,0xF9,0x52,0x2D,0x31,0x09,0xD9,0x7F,0xE2,0x0C,0xEC,0xC6,0x17,0x60,
	0xB7,0x80,0x64,0xB5,0x4A,0xB8,0xFE,0x0D,0x3A,0x10,0x8D,0xD3,0xF2,0xC3,0x47,0x15,0x74,0x88,0x07,0x9F,0xD4,0x31,0x2F,0x97,0x0D,
	0xB8,0xE2,0xF9,0xB7,0x1A,0x49,0xE0,0x7D,0x4D,0x5B,0xD6,0x9D,0x7E,0xF3,0x93,0x82,0x86,0xF1,0xF9,0x6C,0x59,0x92,0x38,0xAE,0xD0,
	0xA3,0x6E,0x57,0x73,0x28,0xCC,0x19,0x8F,0x0E,0xF9,0x01,0x59,0xBA,0x60,0x74,0x8E,0x1B,0xD3,0x8A,0x06,0xA1,0x9F,0x6B,0x76,0xE5,
	0x3E,0xC4,0x37,0xC0,0xB2,0x7A,0xDD,0x50,0x04,0xB3,0x31,0xDD,0x82,0xB3,0x2E,0x71,0xBC,0x8B,0x5A,0x35,0xC7,0x6B,0x9D,0xD8,0x73,
	0xCE,0x3A,0x49,0x39,0x65,0x9F,0x3F,0x54,0x11,0xF8,0xDD,0x8B,0x3A,0x2C,0x76,0x96,0x81,0x6E,0x0E,0xB8,0x62,0xF6,0x88,0x9D,0x35,
	0x35,0xBB,0x66,0x7A,0x67,0x69,0x4C,0x62,0xEA,0xB7,0x68,0x30,0xA2,0x2A,0xB5,0x44,0xE6,0xA5,0x12,0x7A,0x20,0x33,0xE6,0x33,0x92,
	0x9E,0xB4,0x8F,0x1E,0x89,0xD0,0x8E,0xE7,0xF7,0xB1,0x03,0xFE,0xB4,0x4C,0xBE,0x38,0xBB,0xE7,0xA9,0x0D,0xE7,0xA2,0x6A,0x0A,0x82,
	0x2F,0x5C,0xAB,0x79,0x6A,0x15,0x89,0x94,0x2B,0x4E,0x58,0xCF,0xDB,0x93,0x29,0x98,0x74,0x8C,0x24,0x6B,0x9A,0x92,0xEA,0x11,0x18,
	0x1D,0x31,0xA5,0x4D,0xFB,0x49,0xF9,0x16,0x26,0x12,0x03,0x1C,0x9B,0x15,0xCF,0x64,0xBD,0xC1,0x50,0x38,0x0E,0xA4,0x7C,0x90,0x72,
	0xAB,0x9D,0xA2,0x60,0x57,0xD0,0xD0,0x05,0x19,0x30,0x35,0x48,0xD9,0xB9,0x43,0x9D,0x80,0x81,0x93,0xB6,0xC1,0x2D,0x67,0xEF,0xAA,
	0xFD,0x90,0x9E,0x0A,0xAB,0x9A,0x10,0x85,0x5D,0x0D,0xE0,0xD3,0x19,0x6E,0x25,0xAA,0x8D,0x6F,0x25,0x16,0x70,0xF8,0xC7,0x55,0xB9,
	0x74,0xB9,0x22,0xE2,0x28,0x3A,0x2B,0x1B,0x50,0xC7,0x47,0xD9,0xAA,0x7D,0x47,0x7A,0x05,0x58,0x00,0x5F,0x08,0xFC,0x40,0x51,0xA8,
	0x91,0xF1,0x54,0xEF,0x1B,0xEE,0xF1,0xB7,0x3A,0x01,0x5A,0xD0,0x97,0x92,0xCC,0x2B,0x3F,0x8C,0x21,0xE9,0xE8,0xD5,0x34,0x60,0xE1,
	0xF9,0xA6,0x5D,0xAF,0x92,0xEB,0xBD,0x2A,0xF8,0x03,0xB1,0xC0,0xE0,0x44,0x00,0xE2,0xC3,0xEE,0x8C,0xD3,0x69,0xD9,0x1B,0xF6,0x2E,
	0x51,0x8E,0x0E,0x91,0xFD,0xFE,0x81,0x2E,0xB7,0xA5,0x9C,0x9E,0x98,0xC2,0xD9,0x2E,0xB9,0x2B,0x5D,0x50,0xF8,0xDD,0xE1,0x59,0x9C,
	0x08,0x29,0x30,0x0D,0x45,0xEF,0xBB,0xDC,0xC6,0xB1,0xA4,0x72,0x71,0x00,0x87,0x1E,0xAB,0x01,0xB7,0x75,0x5C,0x30,0xCC,0xDD,0x74,
	0x01,0x4B,0xD0,0x5C,0x14,0xB9,0x4F,0xAA,0xCB,0xCE,0xE4,0x56,0x39,0x3C,0xA4,0x1B,0x78,0xA8,0xE1,0x06,0xFA,0x02,0xEE,0xF6,0x79,
	0x27,0x4E,0x61,0xEB,0x85,0x9D,0x67,0x02,0x9A,0x94,0x22,0x47,0x2F,0x64,0xDF,0xF1,0x6E,0x83,0xD3,0x90,0x64,0x87,0x64,0xCD,0xF6,
	0x8A,0x02,0x81,0xE9,0x27,0xBA,0xA0,0x33,0x9E,0xE5,0xFD,0x1A,0x2E,0x3E,0x1D,0xA6,0x15,0x1F,0xB3,0xA9,0x74,0x70,0xB0,0x26,0x7F,
	0xBD,0xC4,0x5B,0x67,0x59,0xA6,0xCD,0x8A,0xC9,0xA7,0xE3,0x9B,0xEE,0xD3,0x0D,0x3A,0x48,0xF6,0xAD,0xB4,0x3C,0x6B,0xDC,0x7C,0xDB,
	0x90,0x96,0x56,0xD5,0x93,0xFE,0x4B,0x70,0x7E,0x79,0xF6,0x95,0x4D,0xB7,0x11,0xC7,0x57,0xA1,0xE5,0xD6,0xAE,0x6C,0x90,0x28,0x05,
	0x69,0x1D,0x76,0x2A,0x3F,0x79,0x18,0xE6,0x5B,0x92,0xED,0x83,0xC6,0x8F,0xF7,0x20,0x38,0x9E,0x2D,0x7A,0xE7,0x5B,0xC6,0x63,0x56,
	0xD3,0x26,0x99,0xE5,0xF8,0x37,0x56,0x39,0xFE,0x36,0x79,0x5E,0x16,0xD0,0x69,0xF5,0x0B,0x80,0xF8,0x9A,0x6F,0x74,0x93,0x73,0xBE,
	0x1B,0x8B,0xA1,0x75,0x04,0x88,0xF5,0xC1,0xA0,0x36,0x34,0xEB,0x40,0x4E,0xFF,0xE9,0x32,0x75,0x86,0x07,0x78,0x62,0xCF,0xF1,0x07,
	0x64,0xF4,0xB6,0xFF,0xB1,0x2C,0xD5,0xCA,0xEB,0x4C,0x3A,0x46,0x57,0xCF,0x0F,0x84,0x21,0x42,0x8F,0x68,0xEC,0x86,0xA0,0x4A,0x6B,
	0x22,0x79,0x3C,0x64,0x45,0xC3,0x3D,0x58,0x13,0xAB,0x9A,0xD3,0xE3,0x2E,0x86,0x1A,0x43,0xB7,0xDB,0x8E,0x31,0x6B,0xC4,0xC5,0xB2,
	0xB5,0x72,0x4D,0x01,0xD2,0xD6,0xA3,0x6A,0x05,0x4F,0x15,0x7F,0x82,0xD5,0xC5,0x7C,0x0A,0x85,0x74,0xE4,0xF9,0x9C,0xA9,0x09,0xB7,
	0x9A,0x49,0x64,0x92,0x0C,0x13,0xCF,0x7A,0xAE,0x11,0x2F,0x40,0x11,0xEF,0x36,0xF3,0xDE,0xAE,0xA0,0x26,0x43,0xCB,0x99,0xCE,0xBB,
	0x84,0x78,0x09,0xD8,0x12,0xDC,0xB1,0x61,0x81,0xD9,0xC9,0x0C,0x0E,0x90,0x7B,0xEB,0x8D,0xE2,0xC2,0x50,0xF4,0x57,0x2C,0x0B,0xC8,
	0x66,0x3F,0x35,0x1A,0x03,0x21,0x9F,0x07,0x55,0xFB,0x29,0x2E,0x94,0x2B,0xD8,0x26,0xEF,0x69,0xEB,0x99,0xC3,0x6A,0xCB,0x44,0xA8,
	0xAC,0x5A,0xC4,0x71,0x2E,0x9E,0xFA,0x33,0x96,0x6E,0x01,0xD3,0xE4,0x58,0x78,0x1A,0x1A,0xC6,0x3B,0x44,0xA6,0x6B,0xC9,0x2E,0x45,
	0x16,0x45,0xDE,0xA6,0xCB,0x21,0x49,0x1C,0xD4,0x37,0x9B,0x9F,0xED,0xEF,0xB5,0x77,0x05,0x14,0x93,0x8D,0x77,0x64,0x29,0xD2,0xB4,
	0x4F,0x72,0xE7,0x16,0x5D,0x58,0x2E,0xA7,0x80,0x99,0x4E,0x23,0x58,0x5A,0x42,0x00,0x9D,0xFE,0xD6,0xA3,0xC8,0x68,0xEC,0xC1,0xF1,
	0x40,0xDA,0x76,0x43,0x62,0x60,0x28,0xC4,0x6C,0xF4,0x24,0x7E,0x00,0x00,0x00,0x00,0x00,0x01,0x85,0x07,0x84,0x0D,0x00,0x00,0x99,
	0x3F,0xEB,0x55,0xA8,0x00,0x0A,0xFC,0x02,0x00,0x00,0x00,0x00,0x00,0x59,0x5A,
};

static enum cmd_status cmd_xz_dec_file_exec(char *cmd)
{
	char *path = cmd;

	XZ_DEC_FILE_ENTER(path, &fp_in, &fp_out);

	struct xz_dec *s;
	struct xz_buf b;
	enum xz_ret xzret;

	uint32_t read_size;
	uint32_t br;
	uint32_t out_len = 0;
	uint8_t *in_buf = malloc(XZ_DEC_INBUF_SIZE);
	if (in_buf == NULL) {
		CMD_ERR("no mem\n");
		return CMD_STATUS_FAIL;
	}

	uint8_t *out_buf = malloc(XZ_DEC_OUTBUF_SIZE);
	if (out_buf == NULL) {
		CMD_ERR("no mem\n");
		return CMD_STATUS_FAIL;
	}
	memset(out_buf, 0, XZ_DEC_OUTBUF_SIZE);

	s = xz_dec_init(XZ_DYNALLOC, XZ_DEC_DICT_MAX);
	if (!s) {
		CMD_ERR("no mem\n");
		free(in_buf);
		return CMD_STATUS_FAIL;
	}

	b.in = in_buf;
	b.in_pos = 0;
	b.in_size = 0;
	b.out = out_buf;
	b.out_pos = 0;
	b.out_size = XZ_DEC_OUTBUF_SIZE;

	CMD_DBG("dec start..\n");
	while (1) {
		if (b.in_pos == b.in_size) {
			if (f_read(&fp_in, in_buf, XZ_DEC_INBUF_SIZE, &read_size) != FR_OK) {
		        CMD_ERR("read failed.\n");
				break;
	    	}
			b.in_size = read_size;
			b.in_pos = 0;
		}

		xzret = xz_dec_run(s, &b);
		if (xzret == XZ_OK || xzret == XZ_STREAM_END) {
			out_len += b.out_pos;
			f_write(&fp_out, out_buf, b.out_pos, &br);
		}
		if (xzret == XZ_OK) {
			b.out_pos = 0;
			continue;
		} else if (xzret == XZ_STREAM_END) {
			CMD_DBG("dec end, size %u --> %u\n", f_size(&fp_in), out_len);
			break;
		} else {
			CMD_ERR("xz_dec_run() fail %d\n", xzret);
			break;
		}
	}

	XZ_DEC_FILE_EXIT(&fp_in, &fp_out);

	xz_dec_end(s);

	free(in_buf);
	free(out_buf);

	return CMD_STATUS_OK;
}

static enum cmd_status cmd_xz_dec_rate_exec(char *cmd)
{
	struct xz_dec *s;
	struct xz_buf b;
	enum xz_ret xzret;

	uint32_t out_len = 0;

	uint8_t *out_buf = malloc(XZ_DEC_OUTBUF_SIZE);
	if (out_buf == NULL) {
		CMD_ERR("no mem\n");
		return CMD_STATUS_FAIL;
	}
	memset(out_buf, 0, XZ_DEC_OUTBUF_SIZE);

	s = xz_dec_init(XZ_DYNALLOC, XZ_DEC_DICT_MAX);
	if (!s) {
		CMD_ERR("no mem\n");
		free(out_buf);
		return CMD_STATUS_FAIL;
	}

	b.in = xz_array;
	b.in_pos = 0;
	b.in_size = sizeof(xz_array);
	b.out = out_buf;
	b.out_pos = 0;
	b.out_size = XZ_DEC_OUTBUF_SIZE;

	printf("b.in_size:%d\n", b.in_size);

	CMD_DBG("dec start..\n");
	uint32_t stop_time = 0;
	uint32_t start_time = OS_TicksToMSecs(OS_GetTicks());

	uint32_t i = 0;
	for (i = 0; i < XZ_RATE_COUNT; i++) {
		b.in_pos = 0;
		b.out_pos = 0;
		while (1) {
			xzret = xz_dec_run(s, &b);
			if (xzret == XZ_OK || xzret == XZ_STREAM_END) {
				out_len += b.out_pos;
				b.out_pos = 0;
			}
			if (xzret == XZ_OK) {
				continue;
			} else if (xzret == XZ_STREAM_END) {
				stop_time = OS_TicksToMSecs(OS_GetTicks());
				//CMD_DBG("dec end, rate: %fKB/S\n", (float)(out_len - b.in_size)/1024/((float)(stop_time-start_time)/1000));
				break;
			} else {
				CMD_ERR("xz_dec_run() fail %d\n", xzret);
				break;
			}
		}
		xz_dec_reset(s);
	}
	if (xzret == XZ_STREAM_END) {
		CMD_DBG("dec end, rate: %fKB/S\n", (float)(out_len - b.in_size * XZ_RATE_COUNT)/1024/((float)(stop_time-start_time)/1000));
	} else
		CMD_ERR("dec fail %d\n", xzret);

	xz_dec_end(s);

	free(out_buf);

	return CMD_STATUS_OK;
}

static const struct cmd_data g_xz_cmds[] = {
	{ "dec-file",		cmd_xz_dec_file_exec },
	{ "dec-rate",		cmd_xz_dec_rate_exec },
};

enum cmd_status cmd_xz_exec(char *cmd)
{
	return cmd_exec(cmd, g_xz_cmds, cmd_nitems(g_xz_cmds));
}
